<?php
/**
 * 
 */

/*
 * 
 */
require_once('StashMapException.php');
require_once('Stashable.php');
/**
 * Stasher Base-Class
 * Useful for caching systems that hold classes
 *
 * @author Anthony Free
 */
abstract class StashMap extends Singleton {
    protected $stashed = array();
    private $stashClass = false;
    
    /**
     * Constructor for the StashMap class
     * @since 20120717
     */
    protected function SingletonConstruct() {}
    
    public function __destruct() {}
    
    /**
     * Function will create new instance of the class, and stash it in this class
     * Returns the created object upon completion.
     * @param type $identifier
     * @param string $stashClass This is the name of the class that your instance will be stashing
     * @param boolean $force_restash Overwrite? 
     */
    public function &stash($identifier, $stashClass, $force_restash = false) {
	
	// Guard against requesting non-existant class
	if(class_exists($stashClass) === false) {
	    throw new RuntimeException("Class `".$stashClass."` not defined!");
	}
	// Guard against missing public methods that must be there
	if(in_array('Stashable', class_implements($stashClass)) === false) {
	    throw new RuntimeException("Class `".$stashClass."` is not Stashable, to fix this please 'implement Stashable' (the interface).!");
	}
	$this->stashClass = $stashClass;
	
	if(isset($this->stashed[$identifier])) {
	    /* Asked for a duplicate item to be stashed?
	     * If they have said they want this, do it and return our new instance back to them instead
	     * Otherwise skip out and treat as a recoverable error on their part and continue
	     */
	    if($force_restash === false) {
		//trigger_error('You requested a duplicate '.$stashClass.' to be stashed by the name of "'.$identifier.'". If this is an error please force a restash.');
		//echo('<pre>'.print_r(debug_backtrace(), true).'</pre>');
		return $this->stashed[$identifier];
	    } else {
		// PHP Has a way of not handling destructors very well, force the issue!
		$this->get($identifier)->__destruct();
		$this->stashed[$identifier] = null;
		unset($this->stashed[$identifier]);
	    }
	}
				    // Be Very Careful with this, this "new variable" is not a typo
	$this->stashed[$identifier] = new $stashClass($identifier);
				    // ----------------------------------------------------------
	if($this->stashed[$identifier]->isValid() === false) {
	    if(isset($this->stashed[$identifier])) {
		$this->get($identifier)->__destruct();
		$this->stashed[$identifier] = null;
		unset($this->stashed[$identifier]);
	    }
	    throw new StashMapException();
	} else {
	    // Return them their instance (This is a compile time reference returned, see method declaration line)
	    $ref =& $this->get($identifier);
	    return $ref;
	}
    }
    
    /**
     * Function will retreive the instance of the class requested, if it exists.
     * @param type $identifier
     * @return type
     * @throws Exception 
     */
    public function &get($identifier, $stashIfNotStashed = true) {
	if(isset($this->stashed[$identifier]) === false && $stashIfNotStashed === true) {
	    $this->stash($identifier, $this->stashClass);
	}
	
	if(isset($this->stashed[$identifier])) {
	    // Again - compile time reference is being returned, see method declaration!
	    return $this->stashed[$identifier];
	} else {
	    // This is a non-recoverable error. Likely a bug or something wacky =-) E:500
	    // UPDATE: This should not ever happen now, as get will call stash.
	    throw new StashMapException('ERROR: The requested '.$stashClass.' referenced by `'.$identifier.'` was requested before it was stashed.');
	}
    }
}

?>
